#include <render/texture2d.h>
#include <graphics/image2d.h>
#include <graphics/image3d.h>
#include <QtOpenGL>
#include <render/glextensions.h>
#include <graphics/utils.h>


namespace yam3d
{
namespace render
{
    namespace
    {
        static const int WRAPPING_LOOKUP[] = { GL_REPEAT, GL_CLAMP };
        static const int FILTER_LOOKUP[] = { GL_NEAREST, GL_LINEAR,
                                GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR,
                                GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR };
        static const int SAMPLER_UNIT_LOOKUP[] = { GL_TEXTURE0, GL_TEXTURE1, GL_TEXTURE2 };

        bool isPowOf2(int s)
        {
            return s==2 || s==4 ||s==8 || s==16 || s==32 || s==64 || s==128 || s==256 || s==512 || s==1024 || s==2048;
        }
    }


Texture::Texture()
    : core::Object(__FUNCTION__)
    , m_textureId(0)
{
    // Use tightly packed data
   // glPixelStorei ( GL_UNPACK_ALIGNMENT, 1 );
   // checkGlError("glPixelStorei(GL_UNPACK_ALIGNMENT, 1)");

    glGenTextures(1, &m_textureId);
    graphics::checkGlError("glGenTextures()");
}

Texture::~Texture()
{
    glDeleteTextures(1, &m_textureId);
    graphics::checkGlError("glDeleteTextures()");
}


Texture2D::Texture2D(graphics::Image2D* img, bool genMipMaps)
    : Texture()
    , m_hasMipMaps(genMipMaps)
    , m_image(img)
{
    assert( img->width() == img->height() );
    assert( isPowOf2(img->width()) );
    assert( isPowOf2(img->height()) );

    int format = img->getFormat() == 4 ? GL_RGBA : GL_RGB;

    glEnable(GL_TEXTURE_2D);
    graphics::checkGlError("glEnable(GL_TEXTURE_2D)");

    // Bind the texture object
    glBindTexture ( GL_TEXTURE_2D, m_textureId );
    graphics::checkGlError("glBindTexture(GL_TEXTURE_2D)");

    // Load the texture
    glTexImage2D ( GL_TEXTURE_2D, 0, format, img->width(), img->height(), 0, format, GL_UNSIGNED_BYTE, img->getPixel(0,0) );
    graphics::checkGlError("glTexImage2D(GL_TEXTURE_2D)");

    if( genMipMaps )
    {
        int level = 1;
        core::Ref<graphics::Image2D> prevImage = img;

        while ( prevImage->width() > 1 && prevImage->height() > 1 )
        {
           core::Ref<graphics::Image2D> newImage = prevImage->genMipMap2D();

           // Load the mipmap level
           glTexImage2D( GL_TEXTURE_2D, level, format, newImage->width(), newImage->height(), 0, format, GL_UNSIGNED_BYTE, newImage->getPixel(0,0) );
           graphics::checkGlError("glTexImage2D(GL_TEXTURE_2D)");

           // Set the previous image for the next iteration
           prevImage = newImage;
           level++;

        }
    }

    glBindTexture ( GL_TEXTURE_2D, 0 );
    graphics::checkGlError("glBindTexture(GL_TEXTURE_2D, 0)");
}

Texture2D::~Texture2D()
{
}

void Texture2D::bind(const graphics::SamplerState* samplerState, int samplerUnit) const
{
    assert( samplerUnit >= 0 && samplerUnit <= 2 );
    assert(glActiveTexture != 0);

    assert( m_hasMipMaps || (samplerState->getMagnifyFilter() != graphics::SamplerState::LINEAR_MIPMAP_LINEAR) );
    assert( m_hasMipMaps || (samplerState->getMagnifyFilter() != graphics::SamplerState::LINEAR_MIPMAP_NEAREST) );
    assert( m_hasMipMaps || (samplerState->getMagnifyFilter() != graphics::SamplerState::NEAREST_MIPMAP_LINEAR) );
    assert( m_hasMipMaps || (samplerState->getMagnifyFilter() != graphics::SamplerState::NEAREST_MIPMAP_NEAREST) );

    glActiveTexture ( SAMPLER_UNIT_LOOKUP[samplerUnit] );
    graphics::checkGlError("glActiveTexture(%d)", samplerUnit);

    glBindTexture ( GL_TEXTURE_2D, m_textureId );
    graphics::checkGlError("glBindTexture(GL_TEXTURE_2D)");

    // Set the default wrapping and filtering modes
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, WRAPPING_LOOKUP[samplerState->getWrappingS()]);
    graphics::checkGlError("glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S)");

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, WRAPPING_LOOKUP[samplerState->getWrappingT()]);
    graphics::checkGlError("glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T)");

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, FILTER_LOOKUP[samplerState->getMinifyFilter()]);
    graphics::checkGlError("glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER)");

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, FILTER_LOOKUP[samplerState->getMagnifyFilter()]);
    graphics::checkGlError("glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER)");

//    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
  //  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 //   glBlendFunc(GL_SRC_ALPHA,                GL_ONE_MINUS_SRC_ALPHA);
 //   glEnable(GL_BLEND);
    // Android
    //    gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    // gl.glEnable(GL10.GL_BLEND);

}

const graphics::Image2D* const Texture2D::getImage() const
{
    return m_image.ptr();
}


Texture3D::Texture3D(graphics::Image3D* img, bool genMipMaps)
    : Texture()
    , m_hasMipMaps(genMipMaps)
{
    glEnable(GL_TEXTURE_3D);
    glBindTexture(GL_TEXTURE_3D, m_textureId);

    glTexImage3D( GL_TEXTURE_3D, 0, GL_RGBA, img->width(), img->height(), img->depth(), 0, GL_RGBA, GL_UNSIGNED_BYTE, img->getPixel(0,0,0) );
    graphics::checkGlError("glTexImage3D(GL_TEXTURE_3D)");

    if( genMipMaps )
    {
        int level = 1;
        graphics::Image3D* prevImage = img;

        while ( prevImage->width() > 1 && prevImage->height() > 1 && prevImage->depth() > 1 )
        {
           graphics::Image3D* newImage = prevImage->genMipMap3D();

           // Load the mipmap level
           glTexImage3D( GL_TEXTURE_3D, 0, GL_RGBA, newImage->width(), newImage->height(), newImage->depth(), 0, GL_RGBA, GL_UNSIGNED_BYTE, newImage->getPixel(0,0,0) );
           graphics::checkGlError("glTexImage3D(GL_TEXTURE_3D)");

           // Delete prec img
           if( prevImage != img )
               delete prevImage;

           // Set the previous image for the next iteration
           prevImage = newImage;
           level++;
        }
    }
}

Texture3D::~Texture3D()
{
}

void Texture3D::bind(const graphics::SamplerState* samplerState, int samplerUnit) const
{
    assert( samplerUnit >= 0 && samplerUnit <= 2 );

    assert( m_hasMipMaps || (samplerState->getMagnifyFilter() != graphics::SamplerState::LINEAR_MIPMAP_LINEAR) );
    assert( m_hasMipMaps || (samplerState->getMagnifyFilter() != graphics::SamplerState::LINEAR_MIPMAP_NEAREST) );
    assert( m_hasMipMaps || (samplerState->getMagnifyFilter() != graphics::SamplerState::NEAREST_MIPMAP_LINEAR) );
    assert( m_hasMipMaps || (samplerState->getMagnifyFilter() != graphics::SamplerState::NEAREST_MIPMAP_NEAREST) );

    assert(glActiveTexture != 0);
    glActiveTexture ( SAMPLER_UNIT_LOOKUP[samplerUnit] );
    glBindTexture ( GL_TEXTURE_3D, m_textureId );
    glEnable(GL_TEXTURE_3D);

    // Set the wrapping and filtering modes
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, WRAPPING_LOOKUP[samplerState->getWrappingS()]);
    graphics::checkGlError("glTexParameteri(GL_TEXTURE_3D,GL_TEXTURE_WRAP_S)");

    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, WRAPPING_LOOKUP[samplerState->getWrappingT()]);
    graphics::checkGlError("glTexParameteri(GL_TEXTURE_3D,GL_TEXTURE_WRAP_T)");

    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, WRAPPING_LOOKUP[samplerState->getWrappingR()]);
    graphics::checkGlError("glTexParameteri(GL_TEXTURE_3D,GL_TEXTURE_WRAP_R)");

    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, FILTER_LOOKUP[samplerState->getMinifyFilter()]);
    graphics::checkGlError("glTexParameteri(GL_TEXTURE_3D,GL_TEXTURE_MIN_FILTER)");

    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, FILTER_LOOKUP[samplerState->getMagnifyFilter()]);
    graphics::checkGlError("glTexParameteri(GL_TEXTURE_3D,GL_TEXTURE_MAG_FILTER)");
}

}
}
